import java.io.InputStream;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

import javax.swing.JOptionPane;

/**
 * Logic for Sudoku game - does <em>not</em> represent the physical game grid.
 * @author Kat.Winter
 */
public class Grid {
	
	private Square[][] square;
	private Row[] row;
	private Column[] col;
	private Section[] section;
	private ArrayList<Integer> possibilities;
	private int size;
	private int numOpts = 5;
	private String fileSelection;
	private Scanner input = null;
	private final String lvlChoice;

	/**
	 * Constructor to build the game grid, also known as the board.
	 * @param size  Size of the Sudoku grid, traditionally 9
	 * @param lvlChoice  Level of difficulty
	 */
	public Grid(int size, String lvlChoice) {
		
		this.lvlChoice = lvlChoice;
		this.size = size;
	}
	
	/**
	 * Builds an initial blank board, initializing the row, column, and
	 * square arrays.
	 * Also calls a method to assign each square to the section it belongs to.
	 * @param size
	 */
	public void initBoard() {
		
		row = new Row[size];
		col = new Column[size];
		section = new Section[size];
		square = new Square[size][size];
		
		for (int i = 0; i < size; i++) {
			
			row[i] = new Row(i);
			col[i] = new Column(i);
			section[i] = new Section(i);
		}
		
		for (int r = 0; r < size; r++) {
			for (int c = 0; c < size; c++) {
				
				square[r][c] = new Square();
			}
		}

		assignSections();
	}
	
	/**
	 * Assigns each square to the section it belongs to.
	 */
	private void assignSections() {
		
		int row = 0;
		int col = 0;
		int limit = (int)Math.sqrt(size);
		
		for (int section = 0; section < size; section++) {
			
			for (int rCount = 0; rCount < limit; rCount++) {
				
				for (int cCount = 0; cCount < limit; cCount++) {
					
					square[row][col].setSection(section);
					col++;
				}
				
				row++;
				
				if ((col % (int)Math.sqrt(size) == 0) 
						&& (row % (int)Math.sqrt(size) == 0)) {
					
					col = col + (int)Math.sqrt(size);
				}
				
				col = col - (int)Math.sqrt(size);
			}
			
			if (col == size) {
				
				col = 0;
				row = row + (int)Math.sqrt(size);
			}
			
			row = row - (int)Math.sqrt(size);
		}
	}
	
	/**
	 * Sets permanent initial value for this square
	 * and passes the information on to the row, column, and section
	 * for this square.
	 * @param r  Row of this square
	 * @param c  column of this square
	 * @param val  Initial value to assign to this square
	 */
	public void addInitial(int r, int c, int val) {
		
		int s = square[r][c].getSection();
		square[r][c].setPermValue(val);
		row[r].addValue(val);
		col[c].addValue(val);
		section[s].addValue(val);
	}
	
	/**
	 * Reads from a text file to add the initial permanent values for each square.
	 * @param newGame  Boolean value for if these initial values are for a new game or not
	 */
	public void setPerms(boolean newGame) {
		
		String[] valFiles = new String[numOpts];
		
		for (int fileNum = 0; fileNum < numOpts; fileNum++) {
			
			valFiles[fileNum] = "resources/" + lvlChoice + "/initVals" + fileNum + ".txt";
		}
		
		int randNum = new Random().nextInt(numOpts);
		
		if (newGame) {
		
			fileSelection = valFiles[randNum];
		}
		
		 ClassLoader loader = this.getClass().getClassLoader();
		
		try {
			
			InputStream stream = loader.getResourceAsStream(fileSelection);
			
			if (stream == null) {
				JOptionPane.showMessageDialog(null, "Resource Not Found");
			}
			
			input = new Scanner (stream);
		} catch (Exception e) {
			
			JOptionPane.showMessageDialog(null, "Problem reading stream");
			System.exit(0);
		}
		
		while (input.hasNext()) {
			
			addInitial(input.nextInt(), input.nextInt(), input.nextInt());
		}
		
		input.close();
	}
	
	/**
	 * Builds an ArrayList consisting of all the possible numbers available
	 * for a given square
	 * @param r  Row of the square
	 * @param c  Column of the square
	 * @return  Returns an ArrayList of the possibilities available for this square
	 */
	public ArrayList<Integer> getPossibilities(int r, int c) {
		
		possibilities = new ArrayList<Integer>();
		int s = square[r][c].getSection();
		
		for (int poss = 1; poss <= size; poss++) {
			
			possibilities.add(new Integer(poss)); 
		}

		for (int poss = 1; poss <= size; poss++) {
			
			boolean remove = false;
			
			if (row[r].isPresent(poss)) {
				remove = true;
			}
			
			if (col[c].isPresent(poss)) {
				remove = true;
			}
			
			if (section[s].isPresent(poss)) {
				remove = true;
			}
			
			if (remove) {
				possibilities.remove(new Integer(poss));
			}
		}
		
		return possibilities;
	}
	
	/**
	 * Finds the section that the square in question belongs to
	 * @param r  Row for the square
	 * @param c  Column for the square
	 * @return  Section that this square is in
	 */
	public int squareSection(int r, int c) {
		
		return square[r][c].getSection();
	}
	
	/**
	 * Checks if this square has a permanent initial value assigned to it
	 * @param r  Row of the square in question
	 * @param c  Column of the square in question
	 * @return  Whether or not this square has a permanent initial value
	 */
	public boolean isSquarePermanent(int r, int c) {
		
		if (square[r][c].isPermanent()) {
			
			return true;
		}
		
		else {
			
			return false;
		}
	}
	
	/**
	 * Checks to see if the given value falls within the
	 * range of available numbers.
	 * @param value  The number that is being checked
	 * @return  True if the input is outside of the range, False if the
	 * input is OK and within range.
	 */
	public boolean invalidInput(int value) {
		
		if (value < 0 || value > size) {
			return true;
		}
		
		else {
			return false;
		}
	}
	
	/**
	 * Checks to see if the player's guess is a valid possibility for this square
	 * @param r  Row of this square
	 * @param c  Column of this square
	 * @param guess  Player's guess
	 * @return  Returns false if it is a valid guess and true if it is not a possibility
	 */
	public boolean invalidPossibility(int r, int c, int guess) {

		if (guess > size) {
			
			return false; // because it's out of range anyway, so doesn't figure into possibilities at all
		}
		
		if (guess == 0) {
			
			return false;
		}
		
		for (Integer i: getPossibilities(r, c)) {
			
			if (i == guess) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * Finds the value held by the square in question
	 * @param r  Row for the square
	 * @param c  Column for the square
	 * @return  Value held by this square
	 */
	public int squareValue(int r, int c) {
		
		return square[r][c].getValue();
	}
	
	/**
	 * Assigns a number value to this square and adds the number to the 
	 * row, column, and section's list of values for this square as well.
	 * If the square had a previous value, in other words it was not blank, then
	 * this removes the previous value from the row, column, and section's lists of
	 * values for this square.
	 * @param r  Row of the square in question
	 * @param c  Column of the square in question
	 * @param guess  Number to be assigned as the new value for this square
	 */
	public void assignValue(int r, int c, int guess) {
		
		int s = square[r][c].getSection();
		
		if (square[r][c].getValue() > 0) {
			row[r].remValue(square[r][c].getValue());
			col[c].remValue(square[r][c].getValue());
			section[s].remValue(square[r][c].getValue());
		}
		
		square[r][c].addValue(guess);
		row[r].addValue(guess);
		col[c].addValue(guess);
		section[s].addValue(guess);
	}
	
	/**
	 * Checks each square to see if it is blank or not - blank is
	 * denoted by a 0. 
	 * @return  True if there is at least one blank square, False if all squares
	 * have values greater than 0.
	 */
	public boolean hasOpening() {
		
		for (int r = 0; r < size; r++) {
			for (int c = 0; c < size; c++) {
				
				if (square[r][c].getValue() == 0) {
					return true;
				}
			}
		}
		
		return false;
	}

}
